#include "pila.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define TAM_INICIAL 50

struct _pila {
    void** datos;
    size_t tamanio;
    size_t cantidad;
};

// Crea una pila.
// Post: devuelve una nueva pila vacía.
pila_t* pila_crear(){

	pila_t *pila = malloc(sizeof(pila_t));
	if (pila == NULL)
		return NULL;

	pila->datos = malloc(TAM_INICIAL * sizeof(void*));
	if (pila->datos == NULL){
		free(pila);
		return NULL;
	}
	pila->tamanio = TAM_INICIAL;
	pila->cantidad = 0;
	return pila;
}

// Destruye la pila.
// Pre: la pila fue creada.
// Post: se eliminaron todos los elementos de la pila.
void pila_destruir(pila_t *pila){
	free(pila->datos);
	free(pila);
}

// Cambia el tamaño de la pila.
// Pre: la pila fue creada.
// Post: la pila cambió de tamaño a nuevo_tam y devuelve true, o la
// pila quedo intacta y devuelve false si no se pudo cambiar el tamaño
// a nuevo_tam.
bool pila_redimensionar(pila_t *pila, size_t tam_nuevo)
{
	void* datos_nuevo = realloc(pila->datos, tam_nuevo * sizeof(void*));
	// Cuando tam_nuevo es 0, es correcto que devuelva NULL.
	// En toda otra situación significa que falló el realloc.
	if (tam_nuevo > 0 && datos_nuevo == NULL)
	    return false;

	pila->datos = datos_nuevo;
	pila->tamanio = tam_nuevo;
	return true;
}

// Agrega un nuevo elemento a la pila. Devuelve falso en caso de error.
// Pre: la pila fue creada.
// Post: se agregó un nuevo elemento a la pila, valor es el nuevo tope.
bool pila_apilar(pila_t *pila, void *valor){
	if (pila->tamanio == pila->cantidad){
		//Se agranda la pila al doble del tamaño actual, porque el 
		//realloc es una opracion lenta O(n).
		if (!pila_redimensionar(pila, pila->tamanio * 2))
			return false;
	}
	pila->datos[pila->cantidad] = valor;
	pila->cantidad += 1;
	return true;
}

// Devuelve verdadero o falso, según si la pila tiene o no elementos apilados.
// Pre: la pila fue creada.
bool pila_esta_vacia(const pila_t *pila){
	return (pila->cantidad == 0);
}

// Saca el elemento tope de la pila. Si la pila tiene elementos, se quita el
// tope de la pila, y se devuelve ese valor. Si la pila está vacía, devuelve 
// NULL.
// Pre: la pila fue creada.
// Post: si la pila no estaba vacía, se devuelve el valor del tope anterior 
// y la pila contiene un elemento menos.
void* pila_desapilar(pila_t *pila){
	if (pila_esta_vacia(pila))
		return NULL;
	//Si falla pila_redimensionar no se informa al usuario el problema
	//y se seguira desapilando aunque se pierda espacio en memoria.
	if (pila->cantidad == (pila->tamanio / 4) && pila->cantidad >= TAM_INICIAL)
		//Se pide memoria igual a la mitad del tamannio cuando la cantidad de datos
		//es un cuarto del tamanio.
		pila_redimensionar(pila, pila->tamanio / 2);

	pila->cantidad -= 1;
	//Se devuelve el elemento en la posicion cantidad y no cantidad-1 porque
	//ya fue achicada en la anterior linea.
	return pila->datos[pila->cantidad];
}

// Obtiene el valor del tope de la pila. Si la pila tiene elementos,
// se devuelve el valor del tope. Si está vacía devuelve NULL.
// Pre: la pila fue creada.
// Post: se devolvió el valor del tope de la pila, cuando la pila no está
// vacía, NULL en caso contrario.
void* pila_ver_tope(const pila_t *pila){
	if (pila_esta_vacia(pila))
		return NULL;
	return pila->datos[pila->cantidad-1];
}
